package deformation.point;

import java.util.List;
import java.util.ListIterator;

import deformation.AbstractPointDeformation;
import deformation.PreCompute;
import grid.GridPoint;
import handler.DeformationPoint;
import handler.PointHandler;

public class AffinePointDeformation extends AbstractPointDeformation<AffinePointDeformation.PreData> {

	
	private final double alpha;
	
	public AffinePointDeformation(PointHandler pHandler) {
		super(pHandler);
		this.alpha=1;
	}
	
	
	public AffinePointDeformation(PointHandler pHandler, double alpha) {
		super(pHandler);
		this.alpha=alpha;
	}
	
	
	/**
	 * 
	 * class contains the precomputed A_j
	 * and the weights which are necessary for the later computation of qStar (and therefore the new GridPoint)
	 *
	 */
	class PreData implements PreCompute{
		double[] A;
		double[] weights;
	}
	
	/**
	 * 
	 * @see deformation.AbstractDeformation#preComputeGridPoint(grid.GridPoint)
	 * 
	 */
	@Override
	public PreData preComputeGridPoint(GridPoint v) {
		//example how to access data
		List<DeformationPoint> basePoints = pointHandler.getBasePoints();
		//example ende
		PreData precomputedData=new PreData();
		
		// compute weights and pStar
		precomputedData.weights=new double[basePoints.size()];
		ListIterator<DeformationPoint> basePointIterator=basePoints.listIterator();
		int i=0;
		
		double sumWeight=0;
		double pStarX=0;
		double pStarY=0;
		
		while (basePointIterator.hasNext()){
			DeformationPoint basePoint=basePointIterator.next();
			precomputedData.weights[i]=1/Math.pow((basePoint.x-v.x)*(basePoint.x-v.x)+(basePoint.y-v.y)*(basePoint.y-v.y),alpha);
			pStarX+=precomputedData.weights[i]*basePoint.x;
			pStarY+=precomputedData.weights[i]*basePoint.y;
			sumWeight+=precomputedData.weights[i];
			i++;
		}
		DeformationPoint pStar = new DeformationPoint(pStarX/sumWeight, pStarY/sumWeight);
	
		//compute the Aj 
		//at first compute the sum pHat_i^t*weights_i*pHat_i
		basePointIterator=basePoints.listIterator();
		double inverseWeightedpHatSum11=0;
		double inverseWeightedpHatSum12=0;
		double inverseWeightedpHatSum21=0;
		double inverseWeightedpHatSum22=0;
		i=0;
		while (basePointIterator.hasNext()){
			DeformationPoint basePoint=basePointIterator.next(); 
			//note the diagonal entries 11 and 22 are exchanged -> to get the inverse
			inverseWeightedpHatSum22+=precomputedData.weights[i]*(basePoint.x-pStar.x)*(basePoint.x-pStar.x);
			inverseWeightedpHatSum12+=precomputedData.weights[i]*(basePoint.x-pStar.x)*(basePoint.y-pStar.y);
			inverseWeightedpHatSum21+=precomputedData.weights[i]*(basePoint.y-pStar.y)*(basePoint.x-pStar.x);
			inverseWeightedpHatSum11+=precomputedData.weights[i++]*(basePoint.y-pStar.y)*(basePoint.y-pStar.y);
		}
		inverseWeightedpHatSum12=-inverseWeightedpHatSum12;
		inverseWeightedpHatSum21=-inverseWeightedpHatSum21;
		//invert the inverseWeightedpHatSum
		double determinantOfWeightedpHatSum=inverseWeightedpHatSum11*inverseWeightedpHatSum22
				-inverseWeightedpHatSum12*inverseWeightedpHatSum21;
		inverseWeightedpHatSum11/=determinantOfWeightedpHatSum;
		inverseWeightedpHatSum12/=determinantOfWeightedpHatSum;
		inverseWeightedpHatSum21/=determinantOfWeightedpHatSum;
		inverseWeightedpHatSum22/=determinantOfWeightedpHatSum;
		
		
		precomputedData.A = new double[basePoints.size()];
		for (int j=0; j<basePoints.size();j++){
			precomputedData.A[j]=precomputedData.weights[j]
					*((v.x-pStar.x)
							*(inverseWeightedpHatSum11*(basePoints.get(j).x-pStar.x)
							+inverseWeightedpHatSum12*(basePoints.get(j).y-pStar.y))
					+(v.y-pStar.y)
							*(inverseWeightedpHatSum21*(basePoints.get(j).x-pStar.x)
							+inverseWeightedpHatSum22*(basePoints.get(j).y-pStar.y)));
		}
		return precomputedData;
	}

	/*
	 * computes and returns qStar=(sum w_*q_i)/(sum w_i)
	 */
	static final DeformationPoint getqStar(List<DeformationPoint> movedPoints, double[] weights){
		double weightsum=0;
		double qStarX=0;
		double qStarY=0;
		for (int i=0;i<movedPoints.size(); i++){
			qStarX+=weights[i]*movedPoints.get(i).x;
			qStarY+=weights[i]*movedPoints.get(i).y;
			weightsum+=weights[i];
		}
		return (new DeformationPoint(qStarX/weightsum,qStarY/weightsum));
	}
	
	/**
	 * 
	 */
	@Override
	public GridPoint calculateGridPoint(GridPoint v, PreData precomputedData) {
		List<DeformationPoint> movedPoints = pointHandler.getMovedPoints(); //the moved handlers, i.e. the q's
		List<DeformationPoint> basePoints = pointHandler.getBasePoints(); 
		DeformationPoint qStar = getqStar(movedPoints, precomputedData.weights); // compute qStar 
		double fX=qStar.x; //x value for the returned GridPoint
		double fY=qStar.y; //y value for the returned GridPoint
		
		for (int i=0;i<movedPoints.size();i++){
			if ((basePoints.get(i).x==v.x) && (basePoints.get(i).y==v.y)) return (new GridPoint(movedPoints.get(i).x,movedPoints.get(i).y));
			fX+=precomputedData.A[i]*(movedPoints.get(i).x-qStar.x);
			fY+=precomputedData.A[i]*(movedPoints.get(i).y-qStar.y);
		}
		return (new GridPoint(fX, fY));
	}

}
